﻿using System;
using System.Collections.Generic;
using System.Text;
using B4ALL.JITL.Contract;
using System.Reflection;
using PostSharp.Laos;
using B4ALL.Common;

namespace B4ALL.JITL
{
    public class JITLService : IJITLService
    {
        #region IJITLService

        public virtual void LoadByRef(Object dummyTargetSrc)
        {

            if (dummyTargetSrc.GetType().GetInterface(typeof(IJITLObject).Name) == null)
                throw new JITLException();

            JITLContext ctx = Context.GetContext<JITLContext>();
            if (ctx == null)
                Context.SetContext(new JITLContext());

            Context.GetContext<JITLContext>().DelayLoad = false;

            IJITLObject dummyTarget = (IJITLObject)dummyTargetSrc;

            Object realObject = null;

            MethodInfo realmInfo = FindMethod(dummyTarget.Reference);

            if (realmInfo == null)
                throw new JITLException("Impossible de trouver la méthode référencée");

            if (dummyTarget.Reference.IsGeneric && dummyTarget.Reference.IsStatic)
            {
                MethodInfo genericMethodCall = realmInfo.MakeGenericMethod(dummyTarget.Reference.GenericArguments);
                realObject = genericMethodCall.Invoke(
                    null,
                    dummyTarget.Reference.MethodParameters
                    );
            }
            else if (dummyTarget.Reference.IsStatic)
            {               
                realObject = realmInfo.Invoke(null, dummyTarget.Reference.MethodParameters);
            }

            dummyTarget.Copy(ref realObject, ref dummyTargetSrc);

            Context.GetContext<JITLContext>().DelayLoad = true;
        }

        /// <summary>
        /// Tests whether or not an object is JITL
        /// </summary>
        /// <param name="eventArgs"></param>
        /// <returns></returns>
        public Boolean IsJITL(Type type)
        {
            if (type.GetInterface(typeof(IJITLObject).Name) != null)
                return true;

            return false;
        }

        /// <summary>
        /// Tests method's ability to return a JITL Object
        /// </summary>
        /// <param name="eventArgs"></param>
        /// <returns></returns>
        public Boolean IsJITL(MethodExecutionEventArgs eventArgs)
        {
            MethodInfo mi = (MethodInfo)eventArgs.Method;
            return IsJITL(mi.ReturnType);
        }

        public virtual IJITLObject NewDummyObject(MethodExecutionEventArgs eventArgs)
        {
            IJITLObject dummyObject = null;

            if (eventArgs.ReturnValue != null && IsJITL(eventArgs.ReturnValue.GetType()))
                dummyObject = (IJITLObject)System.Activator.CreateInstance(eventArgs.ReturnValue.GetType());
            else if (IsJITL(eventArgs))
                dummyObject = (IJITLObject)System.Activator.CreateInstance(((MethodInfo)eventArgs.Method).ReturnType);
            else
                throw new JITLException();

            if (dummyObject != null)
            {
                dummyObject.Reference = new JITLReference((MethodInfo)eventArgs.Method, eventArgs.GetArguments());
            }

            return dummyObject;

        }

        #endregion IJITLService

        #region Helpers

        /// <summary>
        /// Returns methodInfo from reference in JITLObject
        /// </summary>
        /// <param name="target"></param>
        /// <param name="mInfo"></param>
        /// <returns></returns>
        private MethodInfo FindMethod(JITLReference jitlReference)
        {
            MethodInfo[] methods = jitlReference.DeclaringType.GetMethods();

            foreach (MethodInfo method in methods)
            {
                if (method.Name != jitlReference.Name)
                    continue;

                if (method.IsStatic != jitlReference.IsStatic || method.IsGenericMethod != jitlReference.IsGeneric)
                    continue;

                ParameterInfo[] curentMethodParameters = method.GetParameters();

                if (jitlReference.MethodParametersInfo.Length != curentMethodParameters.Length)
                    continue;

                Boolean breaK = false;

                for (int i = 0; i < jitlReference.MethodParametersInfo.Length; i++)
                {
                    if (jitlReference.MethodParametersInfo[i].ParameterType != curentMethodParameters[i].ParameterType)
                    {
                        breaK = true;
                        break;
                    }
                }

                if (breaK)
                    continue;

                return method;
            }

            return null;
        }

        #endregion Helpers
    }
}